﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;

namespace Client
{
    public class GeneticAlgorithm
    {
        //lista dostępnych roślin
        private IList<Plant> plantsList;

        // ilość paliwa w traktorze
        private double tractorFuel;

        // wielkość populacji
        private int populationSize;

        // maksymalna liczba itracji algorytmu
        private int maxNumberOfIteration;

        // maksymalna liczba iteracji bez zmiany najlepszego osobnika
        private int maxIterationWithoutChangeTheBest;

        // liczba osobniów wybieranych w operacji selekcji w metodzie turniejowej
        private int selectionCount;

        // prawdopodobieństwo mutacji
        private double mutationProbability;

        // prawdopodobieństwo krzyżowania
        private double crosoverProbability;

        public GeneticAlgorithm(int tractorFuel)
        {
            this.tractorFuel = tractorFuel;
            populationSize = Properties.Settings.Default.populationSize;
            maxNumberOfIteration = Properties.Settings.Default.maxNumberOfIteration;
            maxIterationWithoutChangeTheBest = Properties.Settings.Default.maxIterationWithoutChangeTheBest;
            selectionCount = Properties.Settings.Default.selectionCount;
            mutationProbability = Properties.Settings.Default.mutationProbability;
            crosoverProbability = Properties.Settings.Default.crosoverProbability;
        }

        private List<Chromosome> InitializePopulation(int populationSize, int countOfFields)
        {
            var result = new List<Chromosome>();
            int maxRand = (int)Math.Pow(Tractor.Map.Size, 2);
            Random r = new Random();
            for (int i = 0; i < populationSize; i++)
            {
                result.Add(new Chromosome(countOfFields, r.Next(0, maxRand)));
            }
            return result;
        }

        // ocena populacji
        private void EvaluatePopulation(List<Chromosome> population)
        {
            foreach (var chromosome in population)
            {
                // oblicz wartosc przystosowania każdego osobnika
                chromosome.Fitness = CalculateFitness(chromosome);
            }

            // posortuj osobniki wg wartośći przysotowania
            population.Sort();

            // ustaw osobniki w populacji od największej funkcji przystosowania do najmniejszej
            population.Reverse();
        }

        private double CalculateFitness(Chromosome chromosome)
        {
            double price = 0;
            double fuelCost = 0;

            for (int i = 0; i < Math.Pow(Tractor.Map.Size, 2); i++)
            {
                if (chromosome.Table[i] == 1)
                {
                    if (Tractor.Map.GetField(i).Plant != null)
                    {
                        Plant plant = getPlantByName(Tractor.Map.GetField(i).Plant);
                        price += plant.profit;
                        fuelCost += plant.fuel_cost;
                    }

                }
            }

            if (fuelCost > tractorFuel)
            {
                return -1;
            }
            else
            {
                return price;
            }
        }

        private void GetPlants()
        {
            using (var dc = new SmartTractorEntities())
            {
                var plants = (from c in dc.plants
                              select c);
                plantsList = plants.ToList<Plant>();
            }
        }

        private Plant getPlantByName(string name)
        {
            foreach (Plant plant in plantsList)
                if (plant.name == name)
                    return plant;
            throw new Exception("Plant not found (" + name + ")");
        }

        public Chromosome RunAlgorithm()
        {
            GetPlants();
            var countOfObject = (int)Math.Pow(Tractor.Map.Size, 2);

            var iteration = 0;
            var stop = false;

            Chromosome theBest = null;

            // inicjalizacja populacji początkowej
            var population = InitializePopulation(populationSize, countOfObject);

            EvaluatePopulation(population);
            theBest = population[0];

            int iterationWithoutChangeTheBest = 0;
            while (!stop)
            {

                var newPopulation = SelectionPopulation(population, selectionCount);

                // krzyzowanie osobnikow
                for (int i = 0; i < newPopulation.Count - 1; i = i + 2)
                {
                    newPopulation[i].Crossover(newPopulation[i + 1], crosoverProbability);
                }

                foreach (var chromosome in newPopulation)
                {
                    chromosome.Mutate(mutationProbability);
                }

                EvaluatePopulation(newPopulation);
                var localTheBest = newPopulation[0];
                if (localTheBest.Fitness > theBest.Fitness)
                {

                    theBest = new Chromosome(localTheBest);
                    iterationWithoutChangeTheBest = 0;
                }
                else
                {
                    iterationWithoutChangeTheBest++;
                }

                // czy wykonano maksymalną ilośc iteracji
                if (iteration >= maxNumberOfIteration)
                {
                    stop = true;
                }
                else
                {
                    iteration++;

                    // brak poprawy wyniku przez kolejne 10 iteracji
                    if (iterationWithoutChangeTheBest > maxIterationWithoutChangeTheBest)
                    {
                        stop = true;
                    }
                }

            }

            for (int i = 0; i < theBest.Table.Count; i++)
            {
                if (theBest.Table[i] == 1)
                {
                    MapField f = Tractor.Map.GetField(i);
                    Tractor.Map.RenderTractor(f.CoordianteX, f.CoordianteY);
                }
            }
            return theBest;

        }

        private List<Chromosome> SelectionPopulation(List<Chromosome> population, int selectionCount)
        {
            var random = new Random();

            var newPopulation = new List<Chromosome>();

            for (int i = 0; i < population.Count; i++)
            {

                var temp = new List<Chromosome>();
                for (int j = 0; j < selectionCount; j++)
                {
                    temp.Add(population[random.Next(population.Count)]);
                }
                temp.Sort();
                newPopulation.Add(temp[temp.Count - 1]);
            }

            return newPopulation;
        }
    }
}
